#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>

#define SET_LED1 _IOR('l', 1, int)
#define SET_LED2 _IOR('l', 2, int)
#define SET_LED3 _IOR('l', 3, int)

#define CNAME "myled"
dev_t devt;
struct class* cls = NULL;
struct device* device = NULL;
struct cdev* cdev = NULL;

struct device_node *node, *child_node;
struct gpio_desc* desc[3] = { NULL };
char* name[3] = { "led1", "led2", "led3" };

int led_open(struct inode* inode, struct file* file)
{
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
ssize_t led_write(struct file* file, const char __user* ubuf, size_t size, loff_t* loffs)
{
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return size;
}
ssize_t led_read(struct file* file, char __user* ubuf, size_t size, loff_t* loffs)
{
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return size;
}
int led_close(struct inode* inode, struct file* file)
{
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
long led_ioctl(struct file* file, unsigned int cmd, unsigned long args)
{
    switch (cmd) {
    case SET_LED1:
        
        if (gpiod_direction_output(desc[0], args)) {
            printk("__%d__:gpiod_direction_output is error\n", __LINE__);
            return -1;
        }
        break;
    case SET_LED2:
        if (gpiod_direction_output(desc[1], args)) {
            printk("__%d__:gpiod_direction_output is error\n", __LINE__);
            return -1;
        }
        break;
    case SET_LED3:
        if (gpiod_direction_output(desc[2], args)) {
            printk("__%d__:gpiod_direction_output is error\n", __LINE__);
            return -1;
        }
        break;
    }
    return 0;
}
const struct file_operations fops = {
    .open = led_open,
    .write = led_write,
    .read = led_read,
    .release = led_close,
    .unlocked_ioctl = led_ioctl,
};
static int __init demo_init(void)
{
    int i = 0;
    node = of_find_node_by_name(NULL, "mynode");
    if (!node) {
        printk("__%d__:of find node by name is error\n", __LINE__);
        return -1;
    }
    child_node = of_get_child_by_name(node, "child-kleds");
    if (!child_node) {
        printk("__%d__:of_get_child_by_name is error\n", __LINE__);
        return -1;
    }
    for (; i < 3; i++) {
        desc[i] = gpiod_get_from_of_node(child_node, name[i], 0, GPIOD_OUT_LOW, NULL);
        if (IS_ERR(desc[i])) {
            printk("__%d__:gpiod_get_from_of_node is error\n", __LINE__);
            return PTR_ERR(desc[i]);
        }

    }

    /***********************自动创建设备节点***********************/
    // 分配struct sdev结构体
    cdev = cdev_alloc();
    if (NULL == cdev) {
        printk("__%d__:cdev_alloc is error\n", __LINE__);
        goto ERR1;
    }
    // 初始化结构体
    cdev_init(cdev, &fops);
    // 指定设备号
    if (alloc_chrdev_region(&devt, 0, 1, CNAME)) {
        printk("__%d__:alloc_chrdev_region is error\n", __LINE__);
        goto ERR2;
    }
    // 对象注册
    if (cdev_add(cdev, devt, 1)) {
        printk("__%d__:cdev_add is error\n", __LINE__);
        goto ERR3;
    }
    // 提交目录
    cls = class_create(THIS_MODULE, CNAME);
    if (IS_ERR(cls)) {
        printk("class_create is error\n");
        goto ERR4;
    }
    // 提交设备

    device = device_create(cls, NULL, devt, NULL, CNAME);
    if (IS_ERR(device)) {
        printk("device_create is error\n");
        goto ERR5;
    }
    /***********************自动创建设备节点***********************/
    return 0;

ERR5:
    class_destroy(cls);
ERR4:
    cdev_del(cdev);
ERR3:
    unregister_chrdev_region(devt, 1);
ERR2:
    kfree(cdev);
ERR1:
    return -1;
}

static void __exit demo_exit(void)
{
    int i = 0;
    for (; i < 3; i++) {
        gpiod_direction_output(desc[i], 0);
        gpiod_put(desc[i]);
    }

    // 取消提交设备节点
    device_destroy(cls, devt);
    // 取消提交目录
    class_destroy(cls);
    // 对象注销
    cdev_del(cdev);
    // 取消设备号
    unregister_chrdev_region(devt, 1);
    // 释放结构体
    kfree(cdev);
}

module_init(demo_init);
module_exit(demo_exit);

MODULE_LICENSE("GPL");